package main

import (
	"context"
	"errors"
	"fmt"
	"github.com/redis/go-redis/v9"
	"log"
	"strconv"
	"sync"
	"time"
)

func init_redis() *redis.Client {
	rdb := redis.NewClient(&redis.Options{
		Addr:     "192.168.149.133:6379",
		Password: "123456", // 密码
		DB:       0,        // 数据库
		PoolSize: 20,       // 连接池大小
	})

	return rdb
	//opt, err := redis.ParseURL("redis://<user>:<pass>@localhost:6379/<db>")
	//if err != nil {
	//	panic(err)
	//}
	//
	//rdb := redis.NewClient(opt)
}

// get set接口调用
func SimpleGetAndSet(rdb *redis.Client) {
	ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
	defer cancel()

	//直接执行命令获取错误
	var j int64
	var keyPrefix = [4]string{"a", "b", "c", "d"}
	for i := 0; i < 4; i++ {
		for j = 0; j < 10; j++ {
			key := keyPrefix[i] + strconv.FormatInt(j, 10)
			val := 10*j + j
			err := rdb.Set(ctx, key, val, time.Hour).Err()
			if err != nil {
				log.Fatalln("set key failed, error :", err)
			}
		}
	}
	//直接执行命令获取值
	val := rdb.Get(ctx, "a1").Val()
	fmt.Println(val)

	//获取命令对象的结果
	val, err := rdb.Get(ctx, "a1").Result()
	if err != nil {
		log.Fatalln("get key failed, error :", err)
	}
	fmt.Println(val, err)

	//获取命令对象
	cmder := rdb.Get(ctx, "a1")
	fmt.Println(cmder.Val())
	fmt.Println(cmder.Err())

}

// 任意命令调用 do接口
func doDemo(rdb *redis.Client) {
	ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
	defer cancel()

	err := rdb.Do(ctx, "hset", "k1", "k10", "10", "ex", "3600").Err()
	if err != nil {
		log.Fatalln("do failed,error :", err)
	}

	val, err := rdb.Do(ctx, "hget", "k1", "k10").Result()
	if err != nil {
		log.Fatalln("do failed,error :", err)
	}
	fmt.Println(val, err)
}

// redis.Nil
// getValueFromRedis redis.Nil判断
func getValueFromRedis(rdb *redis.Client, key, defaultValue string) (string, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
	defer cancel()

	val, err := rdb.Get(ctx, key).Result()
	if err != nil {
		// 如果返回的错误是key不存在
		if errors.Is(err, redis.Nil) {
			return defaultValue, nil
		}
		// 出其他错了
		return "", err
	}
	return val, nil
}

// zset
func zsetDemo(rdb *redis.Client) {
	ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
	defer cancel()

	zsetKey := "language_rank"
	languages := []redis.Z{
		{Score: 90.0, Member: "Golang"},
		{Score: 98.0, Member: "Java"},
		{Score: 95.0, Member: "Python"},
		{Score: 97.0, Member: "JavaScript"},
		{Score: 99.0, Member: "C/C++"},
	}

	err := rdb.ZAdd(ctx, zsetKey, languages...).Err()
	if err != nil {
		log.Fatalln("zadd failed, error :", err)
	}

	newScore, err := rdb.ZIncrBy(ctx, zsetKey, -10.0, "Golang").Result()
	if err != nil {
		log.Fatalln("zincrby failed ,error :", err)
	}
	fmt.Println("zincrby success, newScore:", newScore)

	ret := rdb.ZRangeWithScores(ctx, zsetKey, 0, 2).Val()
	for _, item := range ret {
		fmt.Println(item)
	}
	var opt = redis.ZRangeBy{
		Min: "95",
		Max: "100",
	}
	ret, err = rdb.ZRangeByScoreWithScores(ctx, zsetKey, &opt).Result()
	if err != nil {
		log.Fatalln("ZRangeByScoreWithScores failed ,error: ", err)
	}

	for _, item := range ret {
		fmt.Println(item)
	}
}

// 扫描所有的key，使用scan
func scanKeyDemo(rdb *redis.Client) {
	ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
	defer cancel()

	var cursor uint64
	for {
		var keys []string
		var err error
		//将redis中所有以prefix:为前缀的key都扫描出来
		keys, cursor, err = rdb.Scan(ctx, cursor, "", 1).Result()
		if err != nil {
			log.Fatalln("scan failed, error :", err)
		}
		for _, key := range keys {
			fmt.Println("key", key)
		}

		if cursor == 0 {
			break
		}
	}
}

// 针对遍历大量key，提供iterator方法
func scanKeyDemo2(rdb *redis.Client) {
	ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
	defer cancel()

	iter := rdb.Scan(ctx, 0, "b*", 0).Iterator()
	for iter.Next(ctx) {
		fmt.Println("keys", iter.Val())
	}

	if err := iter.Err(); err != nil {
		panic(err)
	}
}

// 将符合指定规则的key删除
func deleteKeyByMatch(rdb *redis.Client, match string, timeout time.Duration) {
	ctx, cancel := context.WithTimeout(context.Background(), timeout)
	defer cancel()

	iter := rdb.Scan(ctx, 0, match, 0).Iterator()
	for iter.Next(ctx) {
		err := rdb.Del(ctx, iter.Val())
		if err != nil {
			log.Fatalln("del key failed ,error: ", err)
		}
	}

	if err := iter.Err(); err != nil {
		panic(err)
	}
}

// 此外，对于 Redis 中的 set、hash、zset 数据类型，go-redis 也支持类似的遍历方法
func otherIterDemo() {
	//iter := rdb.SScan(ctx, "set-key", 0, "prefix:*", 0).Iterator()
	//iter := rdb.HScan(ctx, "hash-key", 0, "prefix:*", 0).Iterator()
	//iter := rdb.ZScan(ctx, "sorted-hash-key", 0, "prefix:*", 0).Iterator()
}

// pipeline，需要使用exec执行pipe发送执行
func pipeDemo(rdb *redis.Client) {
	ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
	defer cancel()

	val := rdb.Get(ctx, "a1").Val()
	pipe := rdb.Pipeline()
	incr := pipe.Incr(ctx, "a1")
	pipe.Expire(ctx, "a1", time.Hour)

	_, err := pipe.Exec(ctx)
	if err != nil {
		panic(err)
	}
	// 在执行pipe.Exec之后才能获取到结果
	fmt.Println(val, incr.Val())
}

// 函数退出时自动执行exec
func pipelinedDemo(rdb *redis.Client) {
	ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
	defer cancel()

	_, err := rdb.Pipelined(ctx, func(pipe redis.Pipeliner) error {
		pipe.Incr(ctx, "a2")
		pipe.Expire(ctx, "a2", time.Hour)
		return nil
	})
	if err != nil {
		log.Fatalln("pipelined failed, error: ", err)
	}
}

// 查看pipelined每次执行的结果
func GetPipelinedResult(rdb *redis.Client) {
	ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
	defer cancel()
	cmds := func(ctx context.Context, rdb *redis.Client) []redis.Cmder {
		cmds, _ := rdb.Pipelined(ctx, func(pipe redis.Pipeliner) error {
			for i := 0; i < 10; i++ {
				pipe.Get(ctx, fmt.Sprintf("a%d", i))
			}
			return nil
		})
		return cmds
	}(ctx, rdb)

	for _, cmd := range cmds {
		fmt.Println(cmd.(*redis.StringCmd).Val())
	}
}

// 事务
func TransactionDemo(rdb *redis.Client) {
	ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
	defer cancel()
	pipe := rdb.TxPipeline()
	incr := pipe.Incr(ctx, "c1")
	pipe.Expire(ctx, "c1", time.Hour)
	_, err := pipe.Exec(ctx)
	if err != nil {
		log.Fatalln("txpipe failed,error: ", err)
	}
	fmt.Println(incr)
}
func TransactionDemo2(rdb *redis.Client) {
	ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
	defer cancel()
	_, err := rdb.TxPipelined(ctx, func(pipeliner redis.Pipeliner) error {
		pipeliner.Incr(ctx, "c2")
		pipeliner.Expire(ctx, "c2", time.Hour)
		return nil
	})
	if err != nil {
		log.Fatalln("txpipelined failed, error: ", err)
	}

}

// watch
// watchDemo 在key值不变的情况下将其值+1
func watchDemo(rdb *redis.Client, ctx context.Context, key string) error {
	return rdb.Watch(ctx, func(tx *redis.Tx) error {
		n, err := tx.Get(ctx, key).Int()
		if err != nil && err != redis.Nil {
			return err
		}
		// 假设操作耗时5秒
		// 5秒内我们通过其他的客户端修改key，当前事务就会失败
		time.Sleep(5 * time.Second)
		_, err = tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error {
			pipe.Set(ctx, key, n+1, time.Hour)
			return nil
		})
		return err
	}, key)
}

// go-redis 官方文档中使用 GET 、SET和WATCH命令实现一个 INCR 命令的完整示例。
func Demo(rdb *redis.Client) {
	// 此处rdb为初始化的redis连接客户端
	const routineCount = 100

	// 设置5秒超时
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	// increment 是一个自定义对key进行递增（+1）的函数
	// 使用 GET + SET + WATCH 实现，类似 INCR
	increment := func(key string) error {
		txf := func(tx *redis.Tx) error {
			// 获得当前值或零值
			n, err := tx.Get(ctx, key).Int()
			if err != nil && err != redis.Nil {
				return err
			}

			// 实际操作（乐观锁定中的本地操作）
			n++

			// 仅在监视的Key保持不变的情况下运行
			_, err = tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error {
				// pipe 处理错误情况
				pipe.Set(ctx, key, n, 0)
				return nil
			})
			return err
		}

		// 最多重试100次
		for retries := routineCount; retries > 0; retries-- {
			err := rdb.Watch(ctx, txf, key)
			if err != redis.TxFailedErr {
				return err
			}
			// 乐观锁丢失
		}
		return errors.New("increment reached maximum number of retries")
	}

	// 开启100个goroutine并发调用increment
	// 相当于对key执行100次递增
	var wg sync.WaitGroup
	wg.Add(routineCount)
	for i := 0; i < routineCount; i++ {
		go func() {
			defer wg.Done()

			if err := increment("counter3"); err != nil {
				fmt.Println("increment error:", err)
			}
		}()
	}
	wg.Wait()

	n, err := rdb.Get(ctx, "counter3").Int()
	fmt.Println("最终结果：", n, err)

}
func main() {
	rdb := init_redis()
	//SimpleGetAndSet(rdb)
	//doDemo(rdb)
	//getValueFromRedis(rdb, "a1", "defaultVal")
	//zsetDemo(rdb)
	//scanKeyDemo(rdb)
	//scanKeyDemo2(rdb)
	//pipeDemo(rdb)
	//pipelinedDemo(rdb)
	//GetPipelinedResult(rdb)
	//TransactionDemo(rdb)
	//TransactionDemo2(rdb)
	watchDemo(rdb, context.Background(), "c3")
}
